﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace WoWConnecter
{
    public class BasicPacketOut : BinaryWriter
    {
        public int RawId { get; protected set; }
        public virtual int HeaderSize { get { return 1; } }
        public static Encoding DefaultEncoding = Encoding.UTF8;

        public BasicPacketOut(int rawId)
            : base(new MemoryStream())
        {
            RawId = rawId;
        }

        public virtual int Length
        {
            get
            {
                return (int)BaseStream.Length;
            }
        }

        #region WriteByte

        /// <summary>
        /// Writes a byte to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteByte(byte val)
        {
            Write(val);
        }

        /// <summary>
        /// Writes a byte to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteByte(ushort val)
        {
            Write((byte)val);
        }

        /// <summary>
        /// Writes a byte to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteByte(short val)
        {
            Write((byte)val);
        }

        /// <summary>
        /// Writes a byte to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteByte(uint val)
        {
            Write((byte)val);
        }

        /// <summary>
        /// Writes a byte to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteByte(int val)
        {
            Write((byte)val);
        }

        /// <summary>
        /// Writes a byte to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteByte(bool val)
        {
            Write(val);
        }

        #endregion

        #region WriteShort

        /// <summary>
        /// Writes a short to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteShort(byte val)
        {
            Write((short)val);
        }

        /// <summary>
        /// Writes a short to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteShort(ushort val)
        {
            Write(val);
        }

        /// <summary>
        /// Writes a short to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteShort(short val)
        {
            Write(val);
        }

        /// <summary>
        /// Writes a short to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteShort(uint val)
        {
            Write((short)val);
        }

        /// <summary>
        /// Writes a short to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteShort(int val)
        {
            Write((short)val);
        }

        #endregion

        #region WriteInt

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteInt(byte val)
        {
            Write((int)val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteInt(ushort val)
        {
            Write((int)val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteInt(short val)
        {
            Write((int)val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteInt(uint val)
        {
            Write(val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteInt(int val)
        {
            Write(val);
        }

        #endregion

        #region WriteFloat

        /// <summary>
        /// Writes a float to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteFloat(byte val)
        {
            Write((float)val);
        }

        /// <summary>
        /// Writes a float to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteFloat(ushort val)
        {
            Write((float)val);
        }

        /// <summary>
        /// Writes a float to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteFloat(short val)
        {
            Write((int)val);
        }

        /// <summary>
        /// Writes a float to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteFloat(uint val)
        {
            Write((float)val);
        }

        /// <summary>
        /// Writes a float to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteFloat(int val)
        {
            Write((float)val);
        }

        /// <summary>
        /// Writes a float to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteFloat(double val)
        {
            Write((float)val);
        }

        /// <summary>
        /// Writes a float to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteFloat(float val)
        {
            Write(val);
        }

        #endregion

        #region WriteUShort

        /// <summary>
        /// Writes a short to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteUShort(byte val)
        {
            Write((ushort)val);
        }

        /// <summary>
        /// Writes a short to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteUShort(ushort val)
        {
            Write(val);
        }

        /// <summary>
        /// Writes a short to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteUShort(short val)
        {
            Write((ushort)val);
        }

        /// <summary>
        /// Writes a short to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteUShort(uint val)
        {
            Write((ushort)val);
        }

        /// <summary>
        /// Writes a short to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteUShort(int val)
        {
            Write((ushort)val);
        }

        #endregion

        #region WriteUInt

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteUInt(byte val)
        {
            Write((uint)val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteUInt(ushort val)
        {
            Write((uint)val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteUInt(short val)
        {
            Write((uint)val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteUInt(uint val)
        {
            Write(val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteUInt(int val)
        {
            Write((uint)val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteUInt(long val)
        {
            Write((uint)val);
        }

        #endregion

        #region WriteULong

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteULong(byte val)
        {
            Write((ulong)val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteULong(ushort val)
        {
            Write((ulong)val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteULong(short val)
        {
            Write((ulong)val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteULong(uint val)
        {
            Write((ulong)val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteULong(int val)
        {
            Write((ulong)val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteULong(ulong val)
        {
            Write(val);
        }

        /// <summary>
        /// Writes an int to the stream
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteULong(long val)
        {
            Write((ulong)val);
        }

        #endregion

        public override void Write(string str)
        {
            Write(DefaultEncoding.GetBytes(str));
            Write((byte)0);
        }

        /// <summary>
        /// Writes a C-style string to the stream (actual string is null-terminated)
        /// </summary>
        /// <param name="str">String to write</param>
        public virtual void WriteCString(string str)
        {
            Write(DefaultEncoding.GetBytes(str));
            Write((byte)0);
        }

        public virtual void WriteString(string str)
        {
            Write(DefaultEncoding.GetBytes(str));
        }

        /// <summary>
        /// Writes a C-style UTF8 string to the stream (actual string is null-terminated)
        /// </summary>
        /// <param name="str">String to write</param>
        public virtual void WriteUTF8CString(string str)
        {
            Write(Encoding.UTF8.GetBytes(str));
            Write((byte)0);
        }

        /// <summary>
        /// Writes a BigInteger to the stream
        /// </summary>
        /// <param name="bigInt">BigInteger to write</param>
        public virtual void WriteBigInt(BigInteger bigInt)
        {
            byte[] data = bigInt.GetBytes();

            base.Write(data);
        }

        /// <summary>
        /// Writes a BigInteger to the stream
        /// </summary>
        /// <param name="bigInt">BigInteger to write</param>
        /// <param name="length">maximum numbers of bytes to write for th BigInteger</param>
        public virtual void WriteBigInt(BigInteger bigInt, int length)
        {
            byte[] data = bigInt.GetBytes(length);

            base.Write(data);
        }

        /// <summary>
        /// Writes a BigInteger to the stream, while writing the length before it
        /// </summary>
        /// <param name="bigInt">BigInteger to write</param>
        public virtual void WriteBigIntLength(BigInteger bigInt)
        {
            byte[] data = bigInt.GetBytes();

            base.Write((byte)data.Length);
            base.Write(data);
        }

        /// <summary>
        /// Writes a BigInteger to the stream, while writing the length before it
        /// </summary>
        /// <param name="bigInt">BigInteger to write</param>
        /// <param name="length">maximum numbers of bytes to write for th BigInteger</param>
        public virtual void WriteBigIntLength(BigInteger bigInt, int length)
        {
            byte[] data = bigInt.GetBytes(length);

            base.Write((byte)length);
            base.Write(data);
        }

        #region WriteShortBE

        /// <summary>
        /// Writes a short to the stream, in network order
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteShortBE(byte val)
        {
            Write(IPAddress.HostToNetworkOrder(val));
        }

        /// <summary>
        /// Writes a short to the stream, in network order
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteShortBE(short val)
        {
            Write(IPAddress.HostToNetworkOrder(val));
        }

        /// <summary>
        /// Writes a short to the stream, in network order
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteShortBE(int val)
        {
            Write(IPAddress.HostToNetworkOrder((byte)val));
        }

        #endregion

        #region WriteUShortBE
        public void WriteUShortBE(ushort val)
        {
            Write((byte)((val & 0xFF00) >> 8));
            Write((byte)(val & 0x00FF));
        }
        #endregion

        #region WriteIntBE

        /// <summary>
        /// Writes an int to the stream, in network order
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteIntBE(byte val)
        {
            Write(IPAddress.HostToNetworkOrder((int)val));
        }

        /// <summary>
        /// Writes an int to the stream, in network order
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteIntBE(short val)
        {
            Write(IPAddress.HostToNetworkOrder((int)val));
        }

        /// <summary>
        /// Writes an int to the stream, in network order
        /// </summary>
        /// <param name="val">the value to write</param>
        public virtual void WriteIntBE(int val)
        {
            Write(IPAddress.HostToNetworkOrder(val));
        }

        #endregion

        /// <summary>
        /// Writes a long to the stream, in network order
        /// </summary>
        /// <param name="val">the value to write</param>
        public void WriteLongBE(long val)
        {
            Write(IPAddress.HostToNetworkOrder(val));
        }

        /// <summary>
        /// String preceeded by uint length
        /// </summary>
        /// <param name="message"></param>
        public void WriteUIntPascalString(string message)
        {
            if (message.Length > 0)
            {
                var bytes = DefaultEncoding.GetBytes(message);
                WriteUInt(bytes.Length + 1);
                Write(bytes);
                Write((byte)0);
            }
            else
            {
                WriteUInt(0);
            }
        }

        public void WriteByteString(string message)
        {
            if (message.Length > 0)
            {
                WriteByte(message.Length);
                Write(DefaultEncoding.GetBytes(message));
            }
            else
                WriteByte(0);
        }

        public virtual byte[] Finalize()
        {
            BaseStream.Position = 0;
            WriteUShortBE((ushort)(Length - 2));

            if (HeaderSize == 1)
                Write((byte)RawId);
            else
                WriteShort(RawId);

            BaseStream.Position = 0;

            return (BaseStream as MemoryStream).ToArray();
        }
    }
}
